/**
 * 
 */
package vin.echo.core;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import vin.echo.core.configure.EchoConfiguration;
import vin.echo.core.exception.ClassLoopInjectException;
import vin.echo.core.exception.EchoBuildExeception;
import vin.echo.core.exception.ParamenterException;

/**
 * @author Vin.Ma
 * 
 */
public class ServiceContext {

	private static final ServiceContext manager = new ServiceContext();
	private Logger logger = Logger.getLogger(ServiceContext.class);
	private RelationAdapter relationAdapter;

	public static final ServiceContext configure(EchoConfiguration configuration) {
		if (!manager.isInit()) {
			new ContextLoader().loadServices(configuration, manager);
		}
		return manager;
	}

	public static final ServiceContext getInstance() {
		return manager;
	}

	private ConcurrentHashMap<String, Class<?>> services;
	private ConcurrentHashMap<String, Method> methodCache;
	private ConcurrentHashMap<String, Object> instanceCache;
	private boolean isInit = false;

	private ServiceContext() {
		this.services = new ConcurrentHashMap<String, Class<?>>();
		this.methodCache = new ConcurrentHashMap<String, Method>();
		this.instanceCache = new ConcurrentHashMap<String, Object>();

		this.relationAdapter = new RelationAdapter(this);
	}

	/**
	 * 
	 * @param function
	 * @return
	 * @throws ParamenterException
	 */
	public EchoService getService(String serviceName)
			throws ParamenterException {
		EchoService service = null;
		if (instanceCache.containsKey(serviceName)) {
			service = (EchoService) instanceCache.get(serviceName);
		} else if (services.containsKey(serviceName)) {
			service = wakeUpInstance(serviceName);
			try {
				this.relationAdapter.buildObject(service);
			} catch (EchoBuildExeception e) {
				return null;
			}
		}

		return service;
	}

	private EchoService wakeUpInstance(String serviceName)
			throws ParamenterException {
		try {
			Class<?> classCass = services.get(serviceName);
			logger.info("find service :" + classCass.getName());

			EchoService service = (EchoService) classCass.newInstance();
			return service;
		} catch (InstantiationException e) {
			throw new ParamenterException();
		} catch (IllegalAccessException e) {
			throw new ParamenterException();
		}
	}

	private boolean isInit() {
		return isInit;
	}

	/**
	 * 
	 * @param functionName
	 * @param json
	 * @throws ParamenterException
	 */
	public void service(String functionName, String json,
			HttpServletRequest req, HttpServletResponse resp)
			throws ParamenterException {
		String[] functionInfo = functionName.split("\\.");
		
		EchoService service = getService(functionInfo[0]);
		if(service==null){
			logger.warn("no service support for "+functionInfo[0]);
		}else{
			service.setRequest(req);
			service.setResponse(resp);
			
			try {
				invoke(service, functionInfo[1], json);
			} catch (Throwable e) {
				throw new ParamenterException();
			}
		}
	}

	private void invoke(EchoService service, String method, String json)
			throws IllegalArgumentException, IllegalAccessException,
			InvocationTargetException {
		if (service != null && method != null) {

			String functionName = ContextUtils.genFunctionKey(
					service.getClass(), method);

			if (methodCache.containsKey(functionName)) {
				Method realMethod = methodCache.get(functionName);

				new EchoWrapper(service).invoke(realMethod, json);

			}
		}
	}

	public void injectService(String name, Class<?> classClass,
			boolean isProtoType) throws InstantiationException,
			IllegalAccessException, ParamenterException, EchoBuildExeception,
			ClassLoopInjectException {
		services.put(name, classClass);

		this.relationAdapter.adapte(classClass);

		if (isProtoType) {
			Object object = classClass.newInstance();

			this.relationAdapter.buildObject(object);

			instanceCache.put(name, object);
		}
	}

	public void cacheMethod(String functionName, Method m) {
		methodCache.put(functionName, m);
	}

}
